﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Imaging;
using BeerScanatronUI.ScanatronService;
using System.ServiceModel;
using System.Reflection;
using System.Security.Principal;
using System.Collections;
using WebEye.Controls.WinForms.WebCameraControl;

namespace BeerScanatronUI
{
    public partial class Form1 : Form
    {
        /// <summary>
        /// Private service storage.
        /// </summary>
        private ServiceContainer _sc;

        /// <summary>
        /// Container for picture that is passed to/from other dialogs.
        /// </summary>
        public Image StillImage { get; set; }

        /// <summary>
        /// Server plumbing: Binding
        /// </summary>
        public BasicHttpBinding Binding { get; private set; }

        /// <summary>
        /// Server plumbing: Address to services.
        /// </summary>
        public EndpointAddress Address { get; private set; }

        /// <summary>
        /// Require users to confirm UI edits until use check box to stop.
        /// </summary>
        private bool _requireConfirmationForUIEditOperations = true;

        /// <summary>
        /// Set to false until the HR admin password is entered in the Login screen.
        /// </summary>
        private bool _adminAuthenticated = false;

        public Form1()
        {
            // Setup connection to server.
            Uri baseAddress = new Uri(Properties.Settings.Default.ServerDb);
            Address = new EndpointAddress(baseAddress);
            Binding = new BasicHttpBinding();
            Binding.MaxReceivedMessageSize = 1024 * 1024;

            // Register services.
            _sc = new ServiceContainer();
            _sc.AddService(typeof(IServer), new Server(_sc, Binding, Address));

            StillImage = null;
            InitializeComponent();
            this.BackColor = Consts.Theme.BackColor;
            cbStartInKioskMode.Checked = Properties.Settings.Default.KioskModeAtStartup;

            // Move label for beverage reporting to tag so can use it later.
            grpBeverageReport.Tag = grpBeverageReport.Text;
        }

        /// <summary>
        /// Get service helper.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private T GetService<T>()
        {
            Type t = typeof(T);
            return (T)_sc.GetService(t);
        }

        /// <summary>
        /// Override to allow hidden F12 to start manual number print dialog.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (keyData == (Keys.Control | Keys.B))
            {
                ManuallyEnteredBarcodeDialog();
                return true;
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }

        /// <summary>
        /// Get app running.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            IServer server = _sc.GetService(typeof(IServer)) as IServer;
            bool startUpAdminUI = true;
            dateTimePicker_Report.Value = DateTime.Now;

            // Check network server status then start correct UI (Admin or kiosk).
            //  IServer.IsServerPresent() will display messagebox to user if server cannot be found.
            bool isServerPresent = server.IsServerPresent();
            if (cbStartInKioskMode.Checked)
            {
                // Kiosk U.
                using (Kiosk dlg = new Kiosk(_sc, isServerPresent))
                {
                    if (dlg.ShowDialog() == DialogResult.Cancel || !isServerPresent)
                    {
                        // Signal to exit app and don't initialize AdminUI.
                        Application.Exit();
                        startUpAdminUI = false;
                    }
                }
            }
            else if (!isServerPresent)
            {
                // Admin UI.  Freeze screen since server missing.  Otherwise continue on.
                groupGuests.Enabled = false;
                groupBeverages.Enabled = false;
                groupEventSettings.Enabled = false;
                groupCreateScanSheet.Enabled = false;
                menuStrip1.Enabled = false;
                return;
            }

            // Start AdminUI if kiosk never ran or kiosk exited and user wants to run it.
            //  However, never initialize AdminUI if server is missing.
            if (startUpAdminUI && isServerPresent)
            {
                // Enumerate list of cameras and list one available for user.
                List<WebCameraId> cameras = new List<WebCameraId>(webCam2.GetVideoCaptureDevices());
                if (cameras.Count == 0)
                {
                    toolStripMenuCamera1.Text = "(None)";
                }
                else
                {
                    toolStripMenuSelectCamera.DropDownItems.Clear();
                    foreach (WebCameraId id in cameras)
                    {
                        ToolStripMenuItem item = new ToolStripMenuItem();
                        item.Text = id.Name;
                        item.Click += UICameraSelected_Click;
                        item.Checked = (item.Text == Properties.Settings.Default.WebCameraName);

                        // Add camera to collection.
                        toolStripMenuSelectCamera.DropDownItems.Add(item);
                    }
                }

                // Load list of drinkers headers.
                listViewDrinker.Columns.Add("BarCode");
                listViewDrinker.Columns.Add("Last");
                listViewDrinker.Columns.Add("First");
                listViewDrinker.Columns.Add("Can serve?");

                // Load list of beers headers.
                listViewBeer.Columns.Add("BarCode");
                listViewBeer.Columns.Add("Name");
                listViewBeer.Columns.Add("Brand");

                // Load scan list headers
                listScanSheet.Columns.Add("BarCode");
                listScanSheet.Columns.Add("Name");

                // Add images.
                pboxThumbnail.BackColor = Consts.Theme.BackColor;
                pboxChangeKioskPassword.Image = EmbeddedResources.GetBitmap(Consts.Images.Lock);
                pboxChangeKioskPassword.BackColor = Color.Black;
                pboxKiosk.Image = EmbeddedResources.GetBitmap(Consts.Images.Kiosk);
                pboxDocument.Image = EmbeddedResources.GetBitmap(Consts.Images.Document);
                pboxBeverages.Image = EmbeddedResources.GetBitmap(Consts.Images.Beverages);
                pboxHR.Image = EmbeddedResources.GetBitmap(Consts.Images.Admin);
                pboxDocument.BackColor = Consts.Theme.BackColor;

                // Restore state of scansheet.
                RestoreScanlistFromPersistance();

                // Put data on UI.
                UpdateUILists();
            }
        }

        /// <summary>
        /// Get scanlist from settings and restore it.
        /// </summary>
        private void RestoreScanlistFromPersistance()
        {
            IServer server = _sc.GetService(typeof(IServer)) as IServer;

            foreach (string item in Properties.Settings.Default.ScanCodes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                Beer beer = server.GetBeer(item);
                if (beer != null)
                {
                    var newItem = new ListViewItem(new string[] { beer.BarCode, beer.Name });
                    newItem.Name = beer.BarCode; // Needed to look up by key.
                    listScanSheet.Items.Add(newItem);
                }
            }

            // Loop through and size each column header to fit the column header text.
            foreach (ColumnHeader ch in listScanSheet.Columns)
            {
                ch.Width = -2;
            }
            listScanSheet.Sort();
        }

        /// <summary>
        /// Get beers from scan sheet list and persist them.
        /// </summary>
        private void PersistScanlist()
        {
            string setting = "";
            foreach (ListViewItem item in listScanSheet.Items)
            {
                setting += item.Name + ",";
            }

            Properties.Settings.Default.ScanCodes = setting;
            Properties.Settings.Default.Save();
        }

        private void UpdateUILists()
        {
            IServer server = _sc.GetService(typeof(IServer)) as IServer;

            // Load list of drinkers.
            try
            {
                listViewDrinker.Items.Clear();
                IList<Drinker> drinkers = server.GetAllDrinkers();

                if (drinkers.Count() == 0)
                {
                    // Database empty.
                    listViewDrinker.Items.Add(new ListViewItem("*****  No data found  *****"));
                }
                else
                {
                    foreach (var drinker in drinkers)
                    {
                        listViewDrinker.Items.Add(new ListViewItem(
                            new string[] { drinker.BarCode, drinker.LastName, drinker.FirstName, drinker.CanServe ? "Yes" : "No" }));
                    }
                }

                // Loop through and size each column header to fit the column header text.
                foreach (ColumnHeader ch in this.listViewDrinker.Columns)
                {
                    ch.Width = -2;
                }
            }
            catch (Exception ex)
            {
                listViewDrinker.Items.Add("Error while loading data.");
                listViewDrinker.Items.Add(ex.Message);
            }

            // Load list of beers.
            try
            {
                listViewBeer.Items.Clear();
                using (ScaningServiceClient client = new ScaningServiceClient(Binding, Address))
                {
                    var data = client.GetBeers().Select(t => new { t.BarCode, t.Name, t.Brand });

                    if (data.Count() == 0)
                    {
                        // Database empty.
                        listViewBeer.Items.Add(new ListViewItem("*****  No data found  *****"));
                    }
                    else
                    {
                        foreach (var item in data)
                        {
                            listViewBeer.Items.Add(new ListViewItem(new string[] { item.BarCode, item.Name, item.Brand }));
                        }
                    }

                    // Loop through and size each column header to fit the column header text.
                    foreach (ColumnHeader ch in this.listViewBeer.Columns)
                    {
                        ch.Width = -2;
                    }
                }
            }
            catch (Exception ex)
            {
                listViewBeer.Items.Add("Error while loading data.");
                listViewBeer.Items.Add(ex.Message);
            }

            // Final misc. items.
            try
            {
                numMaxBeersPerDay.Maximum = server.GetMaximumBeersPerDay_UpperLimit();
                numMaxBeersPerDay.Value = server.GetBeverageDailyLimit();
                numMaxBeersPerDay.Minimum = 1;
            }
            catch (Exception ex)
            {
                listViewBeer.Items.Add("Error while loading daily beer information.");
                listViewBeer.Items.Add(ex.Message);
            }

            // Force user to reselect image.
            pboxThumbnail.Image = EmbeddedResources.GetBitmap(Consts.Images.HumanSilhouette);
            lblUserBarCode.Text = "";
            lblUserFirstName.Text = "";
            lblUserLastName.Text = "";
            cbCanServe.Checked = false;
        }


        /// <summary>
        /// Save selected camera for when user needs it next.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UICameraSelected_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem item = (ToolStripMenuItem)sender;

            foreach (ToolStripMenuItem camera in toolStripMenuSelectCamera.DropDownItems)
            {
                camera.Checked = (camera.Text == item.Text);
            }

            // Persist camera setting.
            Properties.Settings.Default.WebCameraName = item.Text;
            Properties.Settings.Default.Save();
        }

        #region Print Preview for scan code sheet handlers.

        /// <summary>
        /// Display the print preview dialog for scan code sheet.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCreateScanSheet_Click(object sender, EventArgs e)
        {
            if (listScanSheet.Items.Count > 0)
            {
                printPreviewDialog.Document = printDocument;
                printPreviewDialog.Text = "Print scan sheet";
                printPreviewDialog.ShowDialog();
            }
            else
            {
                MessageBox.Show("You must drag beverages to the scan sheet before attempting to print.",
                    "Cannot print scan sheet",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Scan code print content generator.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            e.Graphics.SmoothingMode = SmoothingMode.None;

            int y = (int)e.MarginBounds.Y;
            using (Font code39Font = new Font("Code39AzaleaRegular1", 60))
            using (Font textFont = new Font("Cambria", 16))
            using (Font textFontSmall = new Font("Cambria", 10))
            {
                // Get dimensions for Code39 image.
                SizeF huge = new SizeF(700, 700);
                SizeF barcodeSize = e.Graphics.MeasureString("*CANCEL*", code39Font, huge);

                int barCodeX = e.MarginBounds.X;
                int barCodeY = (int)barcodeSize.Height;
                int lineDeltaY = (int)((float)barcodeSize.Height * 1.2f);
                int textX = barCodeX + (int)((float)barcodeSize.Width * 1.1f);

                // Get dimenions for text string.
                SizeF textSize = e.Graphics.MeasureString("Hy", textFont, huge);
                int textY = (int)textSize.Height;

                // Separation line.
                int separationLineY = (lineDeltaY - barCodeY) / 2 + barCodeY;

                // First line only - Draw separator line at top.
                e.Graphics.DrawLine(Pens.Black,
                    e.MarginBounds.X, y, e.MarginBounds.Right, y);
                y += (int)((float)barcodeSize.Height * 0.1f);

                // Always put CANCEL code at top.
                e.Graphics.DrawString("*CANCEL*", code39Font, Brushes.Black, barCodeX, y);
                e.Graphics.DrawString("CANCEL", textFont, Brushes.Black, textX, y + (barCodeY - textY) / 2);

                IServer server = GetService<IServer>();
                foreach (ListViewItem item in listScanSheet.Items)
                {
                    string barCode = item.Text;

                    // Draw separator line.
                    e.Graphics.DrawLine(Pens.Black,
                        e.MarginBounds.X, y + separationLineY, e.MarginBounds.Right, y + separationLineY);
                    y += lineDeltaY;

                    // Get beverage name from server.
                    Beer beer = server.GetBeer(barCode);
                    string beverage = "Unknown beverage";
                    if (beer == null)
                    {
                        // See if this is a drinker.
                        Drinker drinker = server.GetDrinker(barCode);
                        if (drinker != null)
                        {
                            beverage = drinker.FullName();
                        }
                    }
                    else
                    {
                        beverage = beer.Name;
                    }

                    // Draw text and barcode.
                    e.Graphics.DrawString("*" + barCode + "*", code39Font, Brushes.Black, barCodeX, y);
                    e.Graphics.DrawString(beverage, textFont, Brushes.Black, textX, y + (barCodeY - textY) / 5);
                    e.Graphics.DrawString(barCode, textFontSmall, Brushes.Black, textX, y + ((barCodeY - textY) / 5) * 4);
                }

                // Draw separator line.
                e.Graphics.DrawLine(Pens.Black,
                    e.MarginBounds.X, y + separationLineY, e.MarginBounds.Right, y + separationLineY);
                y += lineDeltaY;

                // Timestamp
                e.Graphics.DrawString("Printed on " + DateTime.Now.ToShortDateString() + " at " + DateTime.Now.ToShortTimeString(),
                    textFontSmall, Brushes.Black, barCodeX, y);
            }
        }

        #endregion

        #region ScanList handlers and helper methods.

        /// <summary>
        /// Display dialog to allow user to add a single code.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAddToScanSheet_Click(object sender, EventArgs e)
        {
            using (EnterTextDialog dlg = new EnterTextDialog(false))
            {
                bool done = false;
                int i;
                dlg.Text = "New code";
                dlg.Prompt = "Enter new bar code.";

                do
                {
                    if (dlg.ShowDialog() == DialogResult.Cancel)
                    {
                        done = true;
                    }
                    else
                    {
                        string result = dlg.Response.Trim();
                        if (result.Length == 0)
                        {
                            done = true;
                        }
                        else if (result.Contains("*"))
                        {
                            MessageBox.Show("Code cannot have an asterisk character.",
                                "Illegal code", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        else if (result.Contains(" "))
                        {
                            MessageBox.Show("Code cannot have embedded spaces.",
                                "Illegal code", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        else if (!int.TryParse(result, out i))
                        {
                            MessageBox.Show("Code must be an integer.",
                                "Illegal code", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        else
                        {
                            done = true;
                        }
                    }
                }
                while (!done);
            }

            //btnAddToScanSheet.Enabled = lstScanSheet.Items.Count < Consts.MaxScanCodesInList;
        }

        #endregion

        /// <summary>
        /// Allow selected beers to be dragged to scan sheet list.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewBeer_ItemDrag(object sender, ItemDragEventArgs e)
        {
            listViewBeer.DoDragDrop(listViewBeer.SelectedItems, DragDropEffects.Move);
        }

        /// <summary>
        /// Scan sheet must validate that there is usable information in the drag.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listScanSheet_DragEnter(object sender, DragEventArgs e)
        {
            int len = e.Data.GetFormats().Length - 1;
            int i;
            for (i = 0; i <= len; i++)
            {
                if (e.Data.GetFormats()[i].Equals("System.Windows.Forms.ListView+SelectedListViewItemCollection"))
                {
                    //The data from the drag source is moved to the target.	
                    e.Effect = DragDropEffects.Move;
                }
            }
        }

        private void listScanSheet_DragDrop(object sender, DragEventArgs e)
        {
            var x = (ListView.SelectedListViewItemCollection)e.Data
                .GetData("System.Windows.Forms.ListView+SelectedListViewItemCollection");

            // Get beer ids from drag operation.
            foreach (ListViewItem item in x)
            {
                string barCode = item.SubItems[0].Text;
                string name = item.SubItems[1].Text;

                // If in list already, beep and ignore it.
                if (listScanSheet.Items.Find(barCode, false).Any())
                {
                    MessageBox.Show("Duplicated entries are not allowed.", "Duplicate", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else if (listScanSheet.Items.Count > Consts.MaxScanCodesOnPage - 1)
                {
                    MessageBox.Show("Maximum number of list items has been reached.  Remove unneeded items.", 
                        "List full", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                {
                    var newItem = new ListViewItem(new string[] { barCode, name });
                    newItem.Name = barCode; // Needed to look up by key.
                    listScanSheet.Items.Add(newItem);
                }
            }

            // Loop through and size each column header to fit the column header text.
            foreach (ColumnHeader ch in listScanSheet.Columns)
            {
                ch.Width = -2;
            }
            listScanSheet.Sort();
            PersistScanlist();
        }

        private void listScanSheet_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                // Control will only send a single item at a time.
                if (listScanSheet.SelectedIndices.Count > 0)
                {
                    ListView.SelectedIndexCollection item = listScanSheet.SelectedIndices;
                    int index = item[0];
                    string barCode = listScanSheet.Items[index].Name;
                    listScanSheet.Items.RemoveByKey(barCode);

                    if (listScanSheet.Items.Count > 0)
                    {
                        listScanSheet.Items[0].Selected = true;
                    }
                    PersistScanlist();
                }

                // Bypass the control's default handling; 
                // otherwise, remove to pass the event to the default control handler.
                e.Handled = true;
            }
        }
    }
}
